package edu.gatech.ors.mrsim.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;

import org.apache.log4j.Logger;

import edu.gatech.ors.mrsim.calculate.SimTaskTimer;
import edu.gatech.ors.mrsim.io.conf.Configuration;
import edu.gatech.ors.mrsim.io.logging.Logging;

/**
 * Represents the entire system, and contains the simulate() method. 
 * 
 * @author tdoneal
 * 
 */
public class Simulator {
    private static Logger logger = Logging.getInstance().getClassLogger(
	    Simulator.class);

    // the timer to use
    private SimTaskTimer timer;
    
    private Map<String,ModuleSimulator> simulatingSequences;
    
    // the priority queue of events
    private List<Event> events = new ArrayList<Event>();
    
    private ModuleConfig modConfig;

   public Simulator(SimTaskTimer taskTimer,ModuleConfig modConfig){
       this.timer = taskTimer;
       this.modConfig = modConfig;
   }
   
   public ModuleSimulator getModuleSimulatorByID(String modID){
       return simulatingSequences.get(modID);
   }
   
   public void simulate(SimJob job){
       //clear the event queue
       this.events = new ArrayList<Event>();
       
       
       //use the config to get the modules
       List<ModuleSimulator> sims = modConfig.getModuleSimulators();
       //convert list to hashmap based on the module name
       simulatingSequences = new HashMap<String,ModuleSimulator>();
       for (int i= 0;i<sims.size();i++){
	   simulatingSequences.put(sims.get(i).getName(),sims.get(i));
       }
             
      for (String modName:simulatingSequences.keySet()){
	  ModuleSimulator mSim = simulatingSequences.get(modName);
	  //tie each module simulator to this global simulator
	  mSim.setParentSimulator(this);
      }
       
       //now add the initial tasks to the sequential simulators
       Map<String,List<SimTask>> initTasks = job.getInitTasks();
       
       
       
       for (String moduleID:initTasks.keySet()){
	   for (String key:simulatingSequences.keySet()){
		ModuleSimulator sim = simulatingSequences.get(key);
		if (sim.getName().equals(moduleID)){
		    sim.enqueueTasks(initTasks.get(moduleID));
		}
	   }
       }
       
       int rnd = 0;
       //the main simulation loop
       //runs one task at a time
       while( true){
	   
	   System.out.println("Simulation round "+rnd+" beginning");
	   rnd ++;
	   
	   //determine the simulator and task to be run
	   //all this loop is doing is looking for the non-empty simulator with the minimum current timestamp
	   String modMin = null;
	   long minTs = 1000000000000l;
	   for (String modName:simulatingSequences.keySet()){

        	   ModuleSimulator sim = simulatingSequences.get(modName);
        	   
        	   //ignore empty simulators
        	   if (sim.getTasksRemainingCount()==0){
        	       continue;
        	   }
              
        	  long testTs =  sim.getCurrentTimeStamp();
        	  if (testTs<minTs){
        	      modMin = modName;
        	      minTs = testTs;
        	  }
           }
           
           //if we found a simulator to iterate with
           if (modMin!=null){
               //see if this simulator has any tasks left
               ModuleSimulator mSim = simulatingSequences.get(modMin);
               if (mSim.getTasksRemainingCount()>0){
                   //run the task
                   mSim.simulateNextTask();
               }
           }
           
           //we're done with this round of simulation,
           //see if any simulator still has tasks
           boolean allSimsEmpty = true;
	   for (String modName:simulatingSequences.keySet()){

               ModuleSimulator sim = simulatingSequences.get(modName);
               if (sim.getTasksRemainingCount()>0){
        	       allSimsEmpty = false;
        	}
           }
           System.out.println("All sims empty: "+allSimsEmpty);
           if (allSimsEmpty){
               //we're done with the entire simulation
               break;
           }
       }
       
       //sort events
       Collections.sort(events);
       
       System.out.println("Final events list: ");
       //print the ordered events list
       for (int i= 0;i<events.size();i++){
	   System.out.println(events.get(i));
       }
      
   }
   
 
    public SimTaskTimer getTimer(){
	return timer;
    }
    
    public Logger getLogger(){
	return logger;
    }
    
    public List<Event> getEvents(){
	return events;
    }

    public void putEvent(Event event) {
	this.events.add(event);
    }

}
